#include <stdio.h>      /*标准输入输出定义*/
#include <unistd.h>     /*Unix 标准函数定义*/
#include <fcntl.h>      /*文件控制定义*/
#include <string.h>
#include "UtilityTH.h"
#include "SerialPortPosix.h"

SerialPortPosix::SerialPortPosix(const std::string devName)
:m_devName(devName)
{
//    m_devName = devName;
}

bool SerialPortPosix::Open()
{
    int fd = open( m_devName.c_str(), O_RDWR|O_NOCTTY|O_NDELAY);//O_NOCTTY标志说明，该程序不想成为那个端口的"控制终端"。
    if (-1 == fd)
    {
        perror("Can't Open Serial Port");
        return false;
    }

    SetFd(fd);
    //恢复串口为阻塞状态
    if(SetBlocking(fd)<0)
    {
        printf("SetBlocking error\n");
        return false;
    }
//        //恢复串口为阻塞状态
//        if(fcntl(fd, F_SETFL, 0) < 0)//要实现标准的(阻塞)行为，调用 fcntl() 而不带 FNDELAY 项,这同样可以在带O_NDELAY项打开串行端口后使用。
//        {
//            printf("fcntl failed!\n");
//            return -1;
//        }
//        //测试是否为终端设备
//        if(0 == isatty(STDIN_FILENO))
//        {
//            printf("standard input is not a terminal device\n");
//    //        return -1;
//        }
//        else
//        {
//            printf("isatty success!\n");
//        }
    printf("open %s success,fd=%d\n",m_devName.c_str(),GetFd());
    return true;
}

bool SerialPortPosix::SetOpt(speed_t speed,int flowCtrl,int databits,int stopbits,int parity)
{
    struct termios options;
    if  ( tcgetattr(GetFd(),&options)  !=  0)//tcgetattr(fd,&options)得到与fd指向对象的相关参数，并将它们保存于options,该函数还可以测试配置是否正确，该串口是否可用等。若调用成功，函数返回值为0，若调用失败，函数返回值为1.
    {
        printf("tcgetattr error\n");
        return false;
    }
    //设置串口输入波特率和输出波特率
    if(cfsetispeed(&options, speed)==-1)
    {
        printf("in speed set error\n");
        return false;
    }
    if(cfsetospeed(&options, speed)==-1)
    {
        printf("out speed set error\n");
        return false;
    }

    //设置数据流控制
    switch(flowCtrl)
    {
    case 0://不使用流控制
        options.c_cflag &= ~CRTSCTS;
        options.c_iflag &= ~(IXON | IXOFF | IXANY);
        break;
    case 1 ://使用硬件流控制
        options.c_cflag |= CRTSCTS;
        break;
    case 2 ://使用软件流控制
        options.c_iflag |= (IXON | IXOFF | IXANY);

        options.c_cflag |= IXON | IXOFF | IXANY;
        break;
    default://
        printf("Unsupported flowCtrl\n");
        return false;
    }
    //设置数据位
    //屏蔽其他标志位
    options.c_cflag &= ~CSIZE;
    switch (databits)
    {
    case 5    :
        options.c_cflag |= CS5;
        break;
    case 6    :
        options.c_cflag |= CS6;
        break;
    case 7    :
        options.c_cflag |= CS7;
        break;
    case 8:
        options.c_cflag |= CS8;
        break;
    default:
        printf("Unsupported data size\n");
        return false;
    }
    //设置校验位
    switch (parity)
    {
    case 'n':
    case 'N': //无奇偶校验位。
        options.c_cflag &= ~PARENB;//清除校验位
        options.c_iflag &= ~INPCK;//enable parity checking
        break;
    case 'o':
    case 'O'://设置为奇校验
        options.c_cflag |= (PARODD | PARENB); //enable parity //奇校验
        options.c_iflag |= INPCK;//disable parity checking
        break;
    case 'e':
    case 'E'://设置为偶校验
        options.c_cflag |= PARENB;//enable parity
        options.c_cflag &= ~PARODD;//偶校验
        options.c_iflag |= INPCK;//disable pairty checking
        break;
    case 's':
    case 'S': //设置为空格
        options.c_cflag &= ~PARENB; //清除校验位
        options.c_cflag &= ~CSTOPB;
        options.c_iflag |= INPCK;            //disable pairty checking
        break;
    default:
        printf("Unsupported parity\n");
        return false;
    }
    // 设置停止位
    switch (stopbits)
    {
    case 1:
        options.c_cflag &= ~CSTOPB;
        break;
    case 2:
        options.c_cflag |= CSTOPB;
        break;
    default:
        printf("Unsupported stop bits\n");
        return false;
    }

    //c_cflag标志可以定义CLOCAL和CREAD，这将确保该程序不被其他端口控制和信号干扰，同时串口驱动将读取进入的数据。CLOCAL和CREAD通常总是被是能的。
    options.c_cflag |= (CLOCAL | CREAD);
//    //修改控制模式，保证程序不会占用串口
//    options.c_cflag |= CLOCAL;
//    //修改控制模式，使得能够从串口中读取输入数据
//    options.c_cflag |= CREAD;


    //修改输出模式，原始数据输出 注意要使VTIME VMIN这两个字段生效，必须使串口工作于非标准模式。可以通过如下方式设置为RAW模式(非标准模式的一种，多用于通信)
    options.c_oflag &= ~OPOST;//行式输出是通过重置c_oflag成员中的OPOST 项来实现的
    //有时候，在用write发送数据时没有键入回车，信息就发送不出去，这主要是因为我们在输入输出时是按照规范模式接收到回车或换行才发送，而更多情况下我们是不必键入回车或换行的。此时应转换到行方式输入，不经处理直接发送，设置如下：
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);//行式输入是不经处理的。输入字符在接受到后就直接传递，保持原样不处理。一般当你用行方式输入时，你需要去除 ICANON, ECHO, ECHOE, and ISIG 项
    //options.c_lflag &= ~(ISIG | ICANON);

    //存在这样的情况：发送字符0X0d的时候，往往接收端得到的字符是0X0a，原因是因为在串口设置中c_iflag和c_oflag中存在从NL-CR和CR-NL的映射，即串口能把回车和换行当成同一个字符，可以进行如下设置屏蔽之：
    options.c_iflag &= ~ (INLCR | ICRNL | IGNCR);
    options.c_oflag &= ~(ONLCR | OCRNL);

    //设置等待时间和最小接收字符,只有设置为阻塞时这两个参数才有效，仅针对于读操作。
    options.c_cc[VTIME] = 1; /* 读取一个字符等待1*(1/10)s */
    options.c_cc[VMIN] = 1; /* 读取字符的最少个数为1 */
    //处理未接收字符,清空缓存
    tcflush(GetFd(),TCIFLUSH);
    tcflush(GetFd(),TCOFLUSH);


    //激活配置 (将修改后的termios数据设置到串口中）
    if (tcsetattr(GetFd(),TCSANOW,&options) != 0)
    {
        printf("com set error!\n");
        return false;
    }
    return true;
}

int SerialPortPosix::Send(const unsigned char *sendBuf,const int dataLen)
{
    int ret = write(GetFd(),sendBuf,dataLen);
    if(ret>0)
    {
//        printf("Serialport fd: %d, send data success. Data length is %d\n",GetFd(), ret);
    }
//    if ((len < dataLen) && (len!=-1))
//    {
//        tcflush(GetFd(),TCOFLUSH);
//        tcflush(GetFd(),TCIFLUSH);
//    }
    else
    {
        printf("serial send error, fd is %d, ret is %d\n",GetFd(),ret);
        printf("errno=%d\n",errno);
        tcflush(GetFd(),TCOFLUSH);
        tcflush(GetFd(),TCIFLUSH);
    }
    return ret;
}

int SerialPortPosix::Read(unsigned char *readbuf, const int dataLen)
{
    int ret = read(GetFd(),readbuf,dataLen);
    return ret;
}

